#include "stdio.h"
#include "stdlib.h"
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <syslog.h>
#include <string.h>

#include <my_global.h>
#include <mysql.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include "r2_ser2net.h"

typedef struct r2_port_s {
    int port;
    char host[20];
    char user_ip[20];
    int is_connect;
} r2_port_t;

#if defined (CONFIG_R2_ACTION_LOG)    
int r2_mysql_action_log_index;
#endif /* CONFIG_R2_ACTION_LOG */

r2_port_t r2_port_remote[R2_PORT_REMOTE_MAX];

/* ===================Static function =====================================*/
static char *_r2_ser2net_time_print(void);


/*********************Function ********************************************/
/* Function:
 * _r2_ser2net_time_print
 * Features: print current timestamp
 * Return: time with format [hh:mm:ss - dd/mm/yyyy]
 */
static char *
_r2_ser2net_time_print(void)
{
    struct tm *tm_ptr;
    time_t the_time;
    char time_buf[MAX_BUFF];

    time(&the_time);
    tm_ptr = localtime (&the_time);

    sprintf(time_buf, "[%02d:%02d:%02d - %02d/%02d/%04d]",
                        tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec,
                        tm_ptr->tm_mday, tm_ptr->tm_mon+1, tm_ptr->tm_year + 1900);

    return time_buf;
}

/* Function:
 * _r2_ser2net_write_to_log_file
 * Features: open log file and put log to
 * Return value
 *    -    0 : for success
 *    -   -1 : for failure
 */
static int
_r2_ser2net_write_to_log_file(char *log)
{
    FILE *log_fp = NULL;

    log_fp = fopen(R2_SER2NET_LOG_FILE, "a");
    if (NULL == log_fp)
    {
        fprintf(stderr, "Open file %s error\n", R2_SER2NET_LOG_FILE);
        return -1;
    } 

    fprintf(log_fp, "%s %s\n", _r2_ser2net_time_print(), log);
    fclose(log_fp);
    return 0;    
}

/* Function:
 * _r2_ser2net_mysql_port_entry_check
 * Features: check port is exist on mysql database or not
 * Return value
 *     -   0 : exist
 *     -  -1 : doesn't exist
 */
static int
_r2_ser2net_mysql_port_entry_check(int portnum, char *host)
{
    MYSQL mysql;
    MYSQL_RES *res = NULL;
    MYSQL_ROW row;
    char check_cmd[512];
    
    mysql_init(&mysql);
            
    if (mysql_real_connect(&mysql, R2_MYSQL_HOST, R2_MYSQL_USER, R2_MYSQL_PASS, 
                                         R2_MYSQL_DATABASE, 0, NULL, 0) == NULL) 
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);
        return -1;
    }

    snprintf(check_cmd, 512, "SELECT port,server FROM %s WHERE port LIKE '%d' AND server LIKE '%s'",
                                        R2_MYSQL_TABLE, portnum, host);

    if (mysql_query(&mysql, check_cmd)) 
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);
        return -1;
    }

    if (NULL == (res = mysql_store_result(&mysql)))
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);        
        return -1;
    }
        
    if (0 != (row = mysql_fetch_row(res)))
    {
        mysql_free_result(res);
        mysql_close(&mysql);
        return TRUE;
    }

    mysql_free_result(res);
    mysql_close(&mysql);
    return FALSE;
    return 0;
}

/* Function:
 * _r2_ser2net_mysql_update
 * Features:
 * Connect to mysql and update some fields if need
 * Return value: 
 *     -  0 : succesful
 *     - -1 : failed
 */
static int
_r2_ser2net_mysql_update(r2_port_t *p_new)
{
    MYSQL mysql;
    char update_cmd[512];
    time_t the_time;
#if defined (CONFIG_R2_ACTION_LOG)   
    char action_log[512];

    memset(action_log, 0, sizeof (action_log));
#endif /* CONFIG_R2_ACTION_LOG */

    time(&the_time);

    mysql_init(&mysql);
        
    if (mysql_real_connect(&mysql, R2_MYSQL_HOST, R2_MYSQL_USER, R2_MYSQL_PASS, 
                                         R2_MYSQL_DATABASE, 0, NULL, 0) == NULL) 
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);
        return -1;
    }


    if (TRUE == p_new->is_connect)
    {
        snprintf(update_cmd, 512, "UPDATE %s SET state='waiting', user_ip='%s' WHERE port=%d AND server LIKE '%s'", 
                                             R2_MYSQL_TABLE, p_new->user_ip,
                                             p_new->port, p_new->host);

#if defined (CONFIG_R2_ACTION_LOG)
        r2_mysql_action_log_index++;
        snprintf(action_log, 512, "INSERT INTO log %s VALUES (%d,\"%s\",%d,\"%s\",\"connect\",\"%s\")", 
                                             R2_MYSQL_FIELD_STR, 
                                             r2_mysql_action_log_index,
                                             (char *)_r2_ser2net_time_print(),
                                             p_new->port, p_new->host, 
                                             p_new->user_ip);
#endif /* CONFIG_R2_ACTION_LOG */
    }
    else
    {
        snprintf(update_cmd, 512, "UPDATE %s SET state='unconnected', user_ip=DEFAULT WHERE port=%d AND server LIKE '%s'", 
                                             R2_MYSQL_TABLE, p_new->port, 
                                             p_new->host);
#if defined (CONFIG_R2_ACTION_LOG)        
        r2_mysql_action_log_index++;
        snprintf(action_log, 512, "INSERT INTO log %s VALUES (%d,\"%s\",%d,\"%s\",\"disconnect\",\"%s\")",
                                             R2_MYSQL_FIELD_STR, 
                                             r2_mysql_action_log_index, 
                                             (char *)_r2_ser2net_time_print(),
                                             p_new->port, p_new->host, 
                                             p_new->user_ip);
#endif /* CONFIG_R2_ACTION_LOG */
    }
        
    if (mysql_query(&mysql, update_cmd)) 
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);
        return -1;
    }

#if defined (CONFIG_R2_ACTION_LOG)
    /* Add action log to log table of database */    
    if (mysql_query(&mysql, action_log)) 
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);
        return -1;
    }
#endif /* CONFIG_R2_ACTION_LOG */    

    mysql_close(&mysql);
    return 0;
}

/* Function:
 * _r2_ser2net_mysql_update_thread
 * Features:
 *   THREAD function which update new infomation to mysql
 * Return value: none
 */
static void *
_r2_ser2net_mysql_update_thread(void *arg)
{
    r2_port_t tmp;

    memcpy(&tmp, arg, sizeof(r2_port_t));

    if (TRUE == _r2_ser2net_mysql_port_entry_check(tmp.port, tmp.host))
    {
        _r2_ser2net_mysql_update(&tmp);
    }
    else
    {
        _r2_ser2net_write_to_log_file("Port is out of my scope\n");
    }

    pthread_detach(pthread_self());
    pthread_exit(NULL);
}

/* Function:
 * r2_ser2net_mysql_update
 * Features:
 *   EXTERN function to update mysql database
 */
extern int
r2_ser2net_mysql_update (char *port, char *server, struct sockaddr_storage remote, 
                                                                    int connect) 
{
    int res;
    pthread_t a_thread;
    char user_ip[NI_MAXHOST];

    r2_port_t tmp;

    res = getnameinfo((struct sockaddr *) &remote, sizeof(remote), user_ip, 
                                sizeof(user_ip), NULL, 0, NI_NUMERICHOST);

    tmp.port = atoi(port);    
    if (tmp.port == 0)
    {
        return 0;
    }
    
    strcpy(tmp.user_ip, user_ip);
    strcpy(tmp.host, server);
    tmp.is_connect = connect;
    
    /* Using pthread to call a polling program to update showport file */
    res = pthread_create(&a_thread, NULL, _r2_ser2net_mysql_update_thread, (void *)&tmp);
    if (0 != res)
    {
        _r2_ser2net_write_to_log_file("Thread creation failed");
        return -1;
    }
    sleep(1); /* Work around for copy struct value to thread */

    return 0;
}

/* Function:
 * _r2_ser2net_internal_db_check
 * Features:
 *   Scan string from info to get some information to save to tmp struct
 */
static int
_r2_ser2net_internal_db_check (char *info, r2_port_t *tmp)
{
    unsigned int port_t;
    char state[10];
    char temp[30];
 
    if (NULL == info)
        return 0;
 
    memset(temp, 0, sizeof (temp));
    memset(state, 0, sizeof (state));
 
    if (1 == sscanf (info, " connected to (or last connection): %s", temp))
    {
        char *pch;
        pch = strrchr(temp, ':') + 1;
        memset(tmp->user_ip, 0, 20);
        strncpy(tmp->user_ip, temp, pch - temp - 1);
    }
    else if (1 == sscanf (info, " tcp to device state: %s", state))
    {
        if (0 == strcmp(state, "waiting"))
        {
            tmp->is_connect = 1;
        }
        else if (0 == strcmp(state, "unconnected"))
        {
            tmp->is_connect = 0;
        }
        else
        {
            _r2_ser2net_write_to_log_file(state);
        }
    }
     
    return 0;
}

/* Function:
 * _r2_ser2net_mysql_remote_update
 * Features:
 *   Update mysql for remote device
 */
static int
_r2_ser2net_mysql_remote_update(int index)
{
    int sockfd, n;
    char buff[20];
    struct sockaddr_in servaddr;
    char recvline[MAX_BUFF + 1];
    char exit_cmd[]="exit\r";
    int read_finish = 0;
    r2_port_t tmp;
 
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        _r2_ser2net_write_to_log_file("Socket error");
        return -1;
    }
 
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(R2_CONTROL_PORT);
    if (inet_pton(AF_INET, r2_port_remote[index].host, &servaddr.sin_addr) <= 0)
    {
        _r2_ser2net_write_to_log_file("inet_pton error");
        return -1;
    }
 
    if (connect (sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
    {
        _r2_ser2net_write_to_log_file("connect error");
        return -1;
    }
 
    sprintf (buff, "showport %d\r", r2_port_remote[index].port);
 
    sendto(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&servaddr, sizeof (servaddr));

    while(read_finish == 0)
    {
        memset(recvline, 0x0, sizeof (recvline));
        n = recvfrom (sockfd, recvline, sizeof(recvline), 0, NULL, NULL);

        if (n < 20)
            continue;

        if ((n <= MAX_BUFF) && (n > 400)) /* Cheat to break out while loop */
        {
            char *pch;
            pch = strtok (recvline, "\r"); 
            do  
            {       
                pch = strtok (NULL, "\r");
                _r2_ser2net_internal_db_check(pch, &tmp);
            }       
            while(pch != NULL);
             read_finish = 1;
        }
        else
            break;
    }
    
    if (tmp.is_connect != r2_port_remote[index].is_connect)
    {        
        strcpy(tmp.host, r2_port_remote[index].host);
        tmp.port = r2_port_remote[index].port;
        _r2_ser2net_mysql_update(&tmp);
    }

    sendto(sockfd, exit_cmd, sizeof(exit_cmd), 0, (struct sockaddr *)&servaddr,
                                                           sizeof(servaddr));

    return 0;
}

/* Function:
 * _r2_ser2net_mysql_port_entry_remote_get
 * Features:
 *   Get all remote port from mysql database
 */
static int
_r2_ser2net_mysql_port_entry_remote_get()
{
    MYSQL mysql;
    MYSQL_RES *res = NULL;
    MYSQL_ROW row;
    char check_cmd[512];
    int i, j=0;
    
    mysql_init(&mysql);
            
    if (mysql_real_connect(&mysql, R2_MYSQL_HOST, R2_MYSQL_USER, R2_MYSQL_PASS, 
                                         R2_MYSQL_DATABASE, 0, NULL, 0) == NULL) 
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);
        return -1;
    }

    snprintf(check_cmd, 512, "SELECT port,server,state FROM %s WHERE location LIKE 'KOR'",
                                                                R2_MYSQL_TABLE);

    if (mysql_query(&mysql, check_cmd)) 
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);
        return -1;
    }

    if (NULL == (res = mysql_store_result(&mysql)))
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);        
        return -1;
    }
        
    while(row = mysql_fetch_row(res))
    {
        for (i=0; i < mysql_num_fields(res); i++)
        {
            if (i == 0)
            {
                r2_port_remote[j].port = atoi (row[i]);
            }
            else if (i == 1)
            {
                strcpy (r2_port_remote[j].host, row[i]);
            }
            else
            {
                if (0 == strcmp(row[i], "waiting"))
                {
                    r2_port_remote[j].is_connect = 1;
                }
                else if (0 == strcmp(row[i], "unconnected"))
                {
                    r2_port_remote[j].is_connect = 0;
                }
                j++;
            }
        }
    }

    mysql_free_result(res);
    mysql_close(&mysql);
    return 0;
}

/* Function:
 * _r2_ser2net_mysql_remote_polling_thread
 * Features:
 *   THREAD polling to get and update infomation from/to mysql
 */
static void *
_r2_ser2net_mysql_remote_polling_thread(void *arg)
{
    int res;
    pthread_t a_thread;
    int i = 0;
     
    _r2_ser2net_mysql_port_entry_remote_get ();
    while (r2_port_remote[i].port != 0)
    {
        _r2_ser2net_mysql_remote_update(i);
        i++;
    }

    res = pthread_create(&a_thread, NULL, _r2_ser2net_mysql_remote_polling_thread, NULL);
    if (0 != res)
    {
        _r2_ser2net_write_to_log_file("Thread creation failed");
    }

    pthread_detach(pthread_self());
    pthread_exit(NULL);
}

/* Function:
 * r2_ser2net_mysql_remote_polling
 * Features:
 *   EXTERN function polling for remote device
 */
extern int
r2_ser2net_mysql_remote_polling ()
{
    int res;
    pthread_t a_thread;

    memset(r2_port_remote, 0, sizeof (r2_port_t) * R2_PORT_REMOTE_MAX);
    r2_mysql_action_log_index=1;

    /* Using pthread to call a polling program to update showport file */
    res = pthread_create(&a_thread, NULL, _r2_ser2net_mysql_remote_polling_thread, NULL);
    if (0 != res)
    {
        _r2_ser2net_write_to_log_file("Thread creation failed");
    }

    return 0;
}

static int _r2_ser2net_ipaddr_get(char *ipaddr)
{
    int fd;
    struct ifreq ifr;

    fd = socket(AF_INET, SOCK_DGRAM, 0);

    /* I want to get an IPv4 IP address */
    ifr.ifr_addr.sa_family = AF_INET;

    /* I want IP address attached to "eth0" */
    strncpy(ifr.ifr_name, "eth1", IFNAMSIZ-1);

    ioctl(fd, SIOCGIFADDR, &ifr);

    close(fd);

    strcpy (ipaddr, inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));

    return 0;
}

/* Function
 * r2_ser2net_mysql_set_to_default
 * Features:
 * EXTERN function to set all fields in mysql to default value
 */
extern int
r2_ser2net_mysql_set_to_default(char *ipaddr)
{    
    MYSQL mysql;
    char reset_cmd[512];

    mysql_init(&mysql);

    if (mysql_real_connect(&mysql, R2_MYSQL_HOST, R2_MYSQL_USER, R2_MYSQL_PASS, 
                                         R2_MYSQL_DATABASE, 0, NULL, 0) == NULL) 
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);
        return -1;
    }

    snprintf(reset_cmd, 512, "UPDATE %s SET state=DEFAULT, user_ip=DEFAULT WHERE server LIKE '%s'", R2_MYSQL_TABLE, ipaddr);
    if (mysql_query(&mysql, reset_cmd)) 
    {
        _r2_ser2net_write_to_log_file((char *)mysql_error(&mysql));
        mysql_close(&mysql);
        return -1;
    }

    return 0;
}
